package com.jxin.mall.middle.util.redis;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.jxin.mall.middle.consts.MiddleLogMsgConsts;
import com.jxin.mall.middle.exception.RedisUtilException;
import lombok.Data;
import org.apache.commons.collections4.MapUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * redis 工具类
 * @author  蔡佳新
 * @version 1.0
 * @since   jdk 1.8
 */

public abstract class RedisUtil {
    private static final long LONG_ZERO = 0;
    /**令牌默认超时时间为20分钟*/
    private static final long TOKEN_DEF_TTL = 60 * 20;
    

    //================================================common============================================================

    /**
     * 指定缓存失效时间
     * @param  key  键
     * @param  time 时间(秒)
     * @return 指定成功为 <code>true</code>
     * @author 蔡佳新
     */
    public Boolean expire(@NotNull String key, long time) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        if (time > LONG_ZERO) {
           return getRedisTemplate().expire(key, time, TimeUnit.SECONDS);
        }
        return true;
    }

    /**
     * 为流水线操作,提供设置超时时间的方法
     * @param  connection redis连接
     * @param  rawKey     序列化后的redisKey
     * @param  timeOut    超时时间
     * @author 蔡佳新
     */
    private void expire(@NotNull RedisConnection connection, byte[] rawKey, long timeOut) {
        Assert.notNull(connection, RedisUtilMsg.NON_NULL_CONNECTION_REQUIRED);
        if (timeOut <= 0) {
            return;
        }
        final long rawTimeout = TimeoutUtils.toMillis(timeOut, TimeUnit.SECONDS);
        try {
            connection.pExpire(rawKey, rawTimeout);
        } catch (Exception e) {
            // Driver may not support pExpire or we may be running on Redis 2.4
            connection.expire(rawKey, TimeoutUtils.toSeconds(timeOut, TimeUnit.SECONDS));
        }
    }
    /**
     * 根据key 获取过期时间
     * @param  key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     * @author 蔡佳新
     */
    public Long getExpire(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return getRedisTemplate().getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 获得当前key的序列化值
     * @param  key 要序列化的key
     * @return {@link byte[]} 字节码实例
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public byte[] rawKey(@NotNull Object key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return keySerializer().serialize(key);
    }
    /**
     *  获得当前value的序列化值
     * @param  value 要序列化的value
     * @return {@link byte[]} 字节码实例
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public byte[] rawValue(@NotNull Object value) {
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        return valueSerializer().serialize(value);
    }
    /**
     *  获得当前hashKey的序列化值
     * @param  hashKey 散列表的key
     * @return {@link byte[]} 字节码实例
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public byte[] rawHashKey(@NotNull Object hashKey) {
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return hashKeySerializer().serialize(hashKey);
    }
    /**
     *  获得当前hashValue的序列化值
     * @param hashValue 散列表的value
     * @return {@link byte[]} 字节码实例
     * @author 蔡佳新
     */
    @SuppressWarnings("unchecked")
    public byte[] rawHashValue(@NotNull Object hashValue) {
        Assert.notNull(hashValue, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return hashValueSerializer().serialize(hashValue);
    }
    /**
     *  获得当前redis模板的key序列化器
     * @return {@link RedisSerializer} redis序列化器
     * @author 蔡佳新
     */
    private RedisSerializer keySerializer() {
        return getRedisTemplate().getKeySerializer();
    }
    /**
     *  获得当前redis模板的value序列化器
     * @return {@link RedisSerializer} redis序列化器
     * @author 蔡佳新
     */
    private RedisSerializer valueSerializer() {
        return getRedisTemplate().getValueSerializer();
    }
    /**
     * 获得当前redis模板的hashKey序列化器
     * @return {@link RedisSerializer} redis序列化器
     * @author 蔡佳新
     */
    private RedisSerializer hashKeySerializer() {
        return getRedisTemplate().getHashKeySerializer();
    }
    /**
     * 获得当前redis模板的hashValue序列化器
     * @return {@link RedisSerializer} redis序列化器
     * @author 蔡佳新
     */
    private RedisSerializer hashValueSerializer() {
        return getRedisTemplate().getHashValueSerializer();
    }
    //================================================String============================================================

    /**
     * 普通缓存获取
     * @param  key 键
     * @return 值
     * @author 蔡佳新
     */
    @Nullable
    public String get(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return Optional.ofNullable(getRedisTemplate().opsForValue().get(key))
                       .map(String::valueOf)
                       .orElse(null);
    }
    /**
     * 普通缓存获取  指定转义类型
     * @param  key   键
     * @param  clazz 转义类型
     * @return 值
     * @author 蔡佳新
     */
    @Nullable
    public <T> T get(@NotNull String key, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        return Optional.ofNullable(getRedisTemplate().opsForValue().get(key))
                       .map(clazz::cast)
                       .orElse(null);
    }
    /**
     * 普通缓存放入
     * @param  key   键
     * @param  value 值
     * @author 蔡佳新
     */
    public <T> void set(@NotNull String key, @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().opsForValue().set(key, value);
    }

    /**
     * 普通缓存放入并设置时间
     * @param  key   键
     * @param  value 值
     * @param  time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @author 蔡佳新
     */
    public <T> void set(@NotNull String key, @NotNull T value, long time) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        if (time > 0) {
            getRedisTemplate().opsForValue().set(key, value, time, TimeUnit.SECONDS);
            return;
        }
        set(key, value);
    }
    /**
     * 判断key是否存在
     * @param  key 键
     * @return 存在返回 <code>true</code>
     * @author 蔡佳新
     */
    public Boolean hasKey(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return getRedisTemplate().hasKey(key);
    }

    /**
     * 根据key删除缓存
     * @param  keys 可以传一个值 或多个
     * @return 删除的数据个数
     * @author 蔡佳新
     */
    public Long remove(@NotEmpty String... keys) {
        Assert.notEmpty(keys, RedisUtilMsg.NON_NULL_KEYS_REQUIRED);
        if (keys.length == 1) {
            return Boolean.TRUE.equals(getRedisTemplate().delete(keys[0])) ? 1 : LONG_ZERO;
        }
        return getRedisTemplate().delete(Lists.newArrayList(keys));
    }
    //================================================Map===============================================================

    /**
     * HashGet
     * @param  key     键 不能为null
     * @param  hashKey 项 不能为null
     * @return 值
     */
    @Nullable
    public String hget(@NotNull String key, @NotNull String hashKey) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_HASH_KEY_REQUIRED);
        return Optional.ofNullable(getRedisTemplate().opsForHash().get(key, hashKey))
                       .map(String::valueOf)
                       .orElse(null);
    }
    /**
     * HashGet 带转义
     * @param  key     键 不能为null
     * @param  hashKey 项 不能为null
     * @param  clazz   项 转义类型
     * @return 值
     * @author 蔡佳新
     */
    @Nullable
    public <T> T hget(@NotNull String key, @NotNull String hashKey, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_HASH_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        return Optional.ofNullable(getRedisTemplate().opsForHash().get(key, hashKey))
                       .map(clazz::cast)
                       .orElse(null);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param  key 键
     * @return 对应的多个键值, 不存在则为null
     * @author 蔡佳新
     */
    @Nullable
    public Map<String, Object> hmget(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        final Map<Object, Object> entries = getRedisTemplate().opsForHash().entries(key);
        if(MapUtils.isEmpty(entries)){
            return null;
        }
        final Map<String, Object> result = new HashMap<>(entries.size());
        entries.forEach((k,v)-> result.put(String.valueOf(k), v));
        return result;
    }
    /**
     * 获取hashKey对应的所有键值
     * @param  key   键
     * @param  clazz 转义类型
     * @return 对应的多个键值, 不存在则为null
     * @author 蔡佳新
     */
    @Nullable
    public <T> Map<String, T> hmget(@NotNull String key, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        final Map<Object, Object> entries = getRedisTemplate().opsForHash().entries(key);
        if(MapUtils.isEmpty(entries)){
            return null;
        }
        final Map<String, T> result = new HashMap<>(entries.size());
        entries.forEach((k,v)-> result.put(String.valueOf(k), clazz.cast(v)));
        return result;

    }
    /**
     * HashSet
     * @param  key 键
     * @param  map 对应多个键值
     * @author 蔡佳新
     */
    public void hmset(@NotNull String key, @NotEmpty Map<String, Object> map) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(map, RedisUtilMsg.NON_NULL_MAP_REQUIRED);
        getRedisTemplate().opsForHash().putAll(key, map);
    }

    /**
     * HashSet 并设置时间(走流水线模式)
     * @param  key     键
     * @param  map     对应多个键值
     * @param  timeOut 时间(秒)
     * @author 蔡佳新
     */
    public void hmset(@NotNull String key,
                             @NotEmpty Map<String, Object> map,
                             long timeOut) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(map, RedisUtilMsg.NON_NULL_MAP_REQUIRED);
         getRedisTemplate().executePipelined(new RedisCallback<Long>() {
             @Nullable
             @Override
             public Long doInRedis(@Nullable RedisConnection connection) throws DataAccessException {
                 Assert.notNull(connection, "non null RedisConnection");
                 connection.openPipeline();
                 final Map<byte[], byte[]> hashes = new LinkedHashMap<>(map.size());
                 for (Map.Entry<String, Object> entry : map.entrySet()) {
                     hashes.put(rawHashKey(entry.getKey()), rawHashValue(entry.getValue()));
                 }
                 final byte[] rawKey = rawKey(key);
                 connection.hashCommands().hMSet(rawKey, hashes);
                 expire(connection, rawKey, timeOut);
                 return null;
             }
        });
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param  key      键
     * @param  hashKey  项
     * @param  value    值
     * @author 蔡佳新
     */
    public <T> void hset(@NotNull  String key,
                                @NotNull String hashKey,
                                @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_HASH_KEY_REQUIRED);
        Assert.notNull(key, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().opsForHash().put(key, hashKey, value);
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param  key      键
     * @param  hashKey  项
     * @param  value    值
     * @param  timeOut  时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @author 蔡佳新
     */
    public <T> void hset(@NotNull String key,
                          @NotNull String hashKey,
                          @NotNull T value,
                            long timeOut) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_HASH_KEY_REQUIRED);
        Assert.notNull(key, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().executePipelined(new RedisCallback<Long>() {
            @Nullable
            @Override
            public Long doInRedis(@Nullable RedisConnection connection) throws DataAccessException {
                Assert.notNull(connection, "non null RedisConnection");
                connection.openPipeline();
                final byte[] rawKey = rawKey(key);
                connection.hashCommands().hSet(rawKey, rawHashKey(hashKey), rawHashValue(value));
                expire(connection, rawKey, timeOut);
                return null;
            }
        });
    }

    /**
     * 删除hash表中的值
     * @param key      键 不能为null
     * @param hashKeys 项 可以使多个 不能为null
     * @author 蔡佳新
     */
    public Long hdel(@NotNull String key, @NotEmpty Object... hashKeys) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(hashKeys, RedisUtilMsg.NON_EMPTY_HASH_KEYS_REQUIRED);
        return getRedisTemplate().opsForHash().delete(key, hashKeys);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param  key     键 不能为null
     * @param  hashKey 项 不能为null
     * @return 存在则为 <code>true</code>
     */
    public boolean hHasKey(@NotNull String key, @NotNull String hashKey) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(hashKey, RedisUtilMsg.NON_NULL_HASH_KEY_REQUIRED);
        return getRedisTemplate().opsForHash().hasKey(key, hashKey);
    }

    //================================================Set===============================================================

    /**
     * 根据key获取Set中的所有值
     * @param  key 键
     * @return 值
     * @author 蔡佳新
     */
    @Nullable
    public Set<String> sGet(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        final Set<Object> set = getRedisTemplate().opsForSet().members(key);
        if(CollectionUtils.isEmpty(set)){
            return null;
        }
        return set.stream().map(String :: valueOf).collect(Collectors.toSet());
    }
    /**
     * 根据key获取Set中的所有值,指定转换类型
     * @param  key   键
     * @param  clazz 转换类型
     * @return 值
     * @author 蔡佳新
     */
    @Nullable
    public <T> Set<T> sGet(@NotNull String key, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        final Set<Object> set = getRedisTemplate().opsForSet().members(key);
        if(CollectionUtils.isEmpty(set)){
            return null;
        }
        return set.stream().map(clazz::cast).collect(Collectors.toSet());
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param  key   键
     * @param  value 值
     * @return 存在则为 <code>true</code>
     */
    public <T> Boolean sHasKey(@NotNull String key, @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        return getRedisTemplate().opsForSet().isMember(key, value);
    }

    /**
     * 将数据放入set缓存
     * @param  key    键
     * @param  values 值 可以是多个
     * @return 成功个数
     * @author 蔡佳新
     */
    public Long sSet(@NotNull String key, @NotEmpty Object... values) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(values, RedisUtilMsg.NON_EMPTY_VALUES_REQUIRED);
        return getRedisTemplate().opsForSet().add(key, values);
    }

    /**
     * 将set数据放入缓存,并带上超时时间,走流水线
     * todo @NotNull的注解无法使告警消失,无奈用了@Nullable
     * @param  key      键
     * @param  timeOut  时间(秒)
     * @param  values   值 可以是多个
     * @return 成功个数
     */
    public long sSet(@NotNull String key, long timeOut, @NotEmpty Object... values) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(values, RedisUtilMsg.NON_EMPTY_VALUES_REQUIRED);
        final RedisUtil redisUtil = this;
        final List<Object> allResult = getRedisTemplate().executePipelined(new RedisCallback<Long>() {
            @Nullable
            @Override
            public Long doInRedis(@Nullable RedisConnection connection) throws DataAccessException {
                Assert.notNull(connection, RedisUtilMsg.NON_NULL_CONNECTION_REQUIRED);
                connection.openPipeline();
                final byte[] rawKey = rawKey(key);
                connection.sAdd(rawKey, Arrays.stream(values).map(redisUtil::rawValue).toArray(byte[][]::new));
                expire(connection, rawKey, timeOut);
                return null;
            }
        });
        return (long)CollUtil.getFirst(allResult);
    }

    /**
     * 获取set缓存的长度
     * @param  key 键
     * @return set缓存的长度
     * @author 蔡佳新
     */
    public Long sGetSize(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return getRedisTemplate().opsForSet().size(key);
    }

    /**
     * 移除值为value的
     * @param  key    键
     * @param  values 值 可以是多个
     * @return 移除的个数
     * @author 蔡佳新
     */
    public Long sRemove(@NotNull String key, @NotEmpty Object... values) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notEmpty(values, RedisUtilMsg.NON_EMPTY_VALUES_REQUIRED);
       return getRedisTemplate().opsForSet().remove(key, values);

    }
    //================================================list===============================================================

    /**
     * 获取list缓存的内容
     * @param  key   键
     * @param  start 开始
     * @param  end   结束  0 到 -1代表所有值
     * @return list列表
     * @author 蔡佳新
     */
    @Nullable
    public List<String> lGet(@NotNull String key, long start, long end) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        final List<Object> range = getRedisTemplate().opsForList().range(key, start, end);
        if(CollectionUtils.isEmpty(range)){
            return null;
        }
        return range.stream().map(String::valueOf).collect(Collectors.toList());
    }
    /**
     * 获取list缓存的内容
     * @param  key   键
     * @param  start 开始
     * @param  end   结束  0 到 -1代表所有值
     * @param  clazz 转换类型
     * @return list列表
     * @author 蔡佳新
     */
    @Nullable
    public <T> List<T> lGet(@NotNull String key, long start, long end, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        final List<Object> range = getRedisTemplate().opsForList().range(key, start, end);
        if(CollectionUtils.isEmpty(range)){
            return null;
        }
        return range.stream().map(clazz::cast).collect(Collectors.toList());
    }
    /**
     * 获取list缓存的长度
     * @param  key 键
     * @return list列表的长度
     * @author 蔡佳新
     */
    public Long lGetSize(@NotNull String key) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return getRedisTemplate().opsForList().size(key);
    }

    /**
     * 通过索引 获取list中的值
     * @param key   键
     * @param index 索引  index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
     * @return 指定位置元素对象
     * @author 蔡佳新
     */
    @Nullable
    public String lGetIndex(@NotNull String key, long index) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        return String.valueOf(getRedisTemplate().opsForList().index(key, index));
    }
    /**
     * 通过索引 获取list中的值,并转义
     * @param  key   键
     * @param  index 索引  index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
     * @param  clazz 转义的类型
     * @return 指定位置元素对象
     * @author 蔡佳新
     */
    @Nullable
    public <T> T lGetIndex(@NotNull String key, long index, @NotNull Class<T> clazz) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(clazz, RedisUtilMsg.NON_NULL_CLAZZ_REQUIRED);
        return clazz.cast(getRedisTemplate().opsForList().index(key, index));
    }
    /**
     * 将list放入缓存
     * @param  key   键
     * @param  value 值
     * @author 蔡佳新
     */
    public  <T> void lSet(@NotNull String key, @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().opsForList().rightPush(key, value);
    }

    /**
     * 将list放入缓存,带超时时间,走流水线
     * @param  key     键
     * @param  value   值
     * @param  timeOut 时间(秒)
     * @author 蔡佳新
     */
    public <T> void lSet(@NotNull String key, @NotNull T value, long timeOut) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().executePipelined(new RedisCallback<Long>() {
            @Nullable
            @Override
            public Long doInRedis(@Nullable RedisConnection connection) throws DataAccessException {
                Assert.notNull(connection, "non null RedisConnection");
                connection.openPipeline();
                final byte[] rawKey = rawKey(key);
                connection.rPush(rawKey, rawValue(value));
                expire(connection, rawKey, timeOut);
                return null;
            }
        });
    }

    /**
     * 将list放入缓存
     * @param  key    键
     * @param  values 值
     * @author 蔡佳新
     */
    public void lSet(@NotNull String key, @NotEmpty List<Object> values) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(values, RedisUtilMsg.NON_EMPTY_VALUES_REQUIRED);
        getRedisTemplate().opsForList().rightPushAll(key, values);
    }

    /**
     * 将list放入缓存,带超时时间,走流水线
     * @param  key     键
     * @param  values  值
     * @param  timeOut 时间(秒)
     * @return 插入的条数
     * @author 蔡佳新
     */
    public Long lSet(@NotNull String key, @NotEmpty List<Object> values, long timeOut) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(values, RedisUtilMsg.NON_EMPTY_VALUES_REQUIRED);
        final RedisUtil redisUtil = this;
        final List<Object> allResult = getRedisTemplate().executePipelined(new RedisCallback<Long>() {
            @Nullable
            @Override
            public Long doInRedis(@Nullable RedisConnection connection) throws DataAccessException {
                Assert.notNull(connection, "non null RedisConnection");
                connection.openPipeline();
                final byte[] rawKey = rawKey(key);
                connection.rPush(rawKey, values.stream().map(redisUtil::rawValue).toArray(byte[][]::new));
                expire(connection, rawKey, timeOut);
                return null;
            }
        });
        return (long)CollUtil.getFirst(allResult);
    }

    /**
     * 根据索引修改list中的某条数据
     * @param  key   键
     * @param  index 索引
     * @param  value 值
     * @author 蔡佳新
     */
    public <T> void lUpdateIndex(@NotNull String key, long index, @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        getRedisTemplate().opsForList().set(key, index, value);
    }

    /**
     * 移除N个值为value
     * @param  key   键
     * @param  count 移除多少个
     * @param  value 值
     * @return 移除的个数
     * @author 蔡佳新
     */
    public  <T> Long lRemove(@NotNull String key, long count, @NotNull T value) {
        Assert.notNull(key, RedisUtilMsg.NON_NULL_KEY_REQUIRED);
        Assert.notNull(value, RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        return getRedisTemplate().opsForList().remove(key, count, value);
    }
    //---------------------------------------------------token---------------------------------------------------------

    /**
     * 创建令牌
     * @param  prefix 前缀
     * @param  value  参数
     * @author 蔡佳新
     */
    public <T> String createToken(@NotNull String prefix, @NotNull T value){
        final String token = IdUtil.objectId();
        createToken(prefix, token, value, TOKEN_DEF_TTL);
        return token;
    }
    /**
     * 创建令牌
     * @param  prefix 前缀
     * @param  token  令牌
     * @param  value  参数
     * @param  outTime 超时时间
     * @author 蔡佳新
     */
    public <T> void createToken(@NotNull String prefix,
                                       @NotNull String token,
                                       @NotNull T value,
                                       long outTime){
        Assert.notNull(prefix, RedisUtilMsg.NON_NULL_PREFIX_REQUIRED);
        Assert.notNull(token,  RedisUtilMsg.NON_NULL_TOKEN_REQUIRED);
        Assert.notNull(value,  RedisUtilMsg.NON_NULL_VALUE_REQUIRED);
        hset(prefix, token, value, outTime);
    }
    /**
     * 删除令牌
     * @param  prefix 前缀
     * @param  token  令牌
     * @return 删除成功则返回 <code>true</code>
     * @author 蔡佳新
     */
    public Boolean removeToken(@NotNull String prefix, @NotNull String token){
        Assert.notNull(prefix, RedisUtilMsg.NON_NULL_PREFIX_REQUIRED);
        Assert.notNull(token, RedisUtilMsg.NON_NULL_TOKEN_REQUIRED);
        return hdel(prefix, token) > 0;
    }
    /**
     * 获取令牌参数
     * @param  prefix 前缀
     * @param  token  令牌
     * @return 令牌中存的数据
     * @author 蔡佳新
     */
    public <T> T getTokenValue(@NotNull String prefix, @NotNull String token, @NotNull Class<T> clazz){
        Assert.notNull(prefix, RedisUtilMsg.NON_NULL_PREFIX_REQUIRED);
        Assert.notNull(token, RedisUtilMsg.NON_NULL_TOKEN_REQUIRED);
        return hget(prefix, token, clazz);
    }
    /**
     * 判断令牌是否存在
     * @param  prefix 前缀
     * @param  token  令牌
     * @return 存在 <code>true</code>
     * @author 蔡佳新
     */
    public Boolean hasToken(@NotNull String prefix, @NotNull String token){
        Assert.notNull(prefix, RedisUtilMsg.NON_NULL_PREFIX_REQUIRED);
        Assert.notNull(token, RedisUtilMsg.NON_NULL_TOKEN_REQUIRED);
        return hasKey(StrUtil.builder(prefix, token).toString());
    }

    public abstract RedisTemplate<String, Object> getRedisTemplate();
    //---------------------------------------------------logMsg---------------------------------------------------------
    /**
     * redis工具类 的内部消息类
     * @author  蔡佳新
     * @version 1.0
     * @since   jdk 1.8
     */
    protected interface RedisUtilMsg{
        /**key为null的异常信息*/
        String NON_NULL_KEY_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null key required";
        /**前缀为null的异常信息*/
        String NON_NULL_PREFIX_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null prefix required";
        /**token为null的异常信息*/
        String NON_NULL_TOKEN_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null token required";
        /**value为null的异常信息*/
        String NON_NULL_VALUE_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null value required";
        /**连接为null的异常信息*/
        String NON_NULL_CONNECTION_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null connection required";
        /**类型为null的异常信息*/
        String NON_NULL_CLAZZ_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null clazz required";
        /**keys为空的异常信息*/
        String NON_NULL_KEYS_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non empty keys required";
        /**hashKey为null的异常信息*/
        String NON_NULL_HASH_KEY_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non null hashKey required";
        /**map为空的异常信息*/
        String NON_NULL_MAP_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non empty map required";
        /**hashKeys为空的异常信息*/
        String NON_EMPTY_HASH_KEYS_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non empty hashKeys required";
        /**values为空的异常信息*/
        String NON_EMPTY_VALUES_REQUIRED = MiddleLogMsgConsts.REDIS_UTIL + "non empty values required";
    }
}
